﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Opennav.MapGenerator.Map.Locations;
using Opennav.MapGenerator.Map.Roads;
using System.IO;
using Opennav.MapGenerator.Map.Labels;
using System.Diagnostics;

namespace Opennav.MapGenerator.Map.Search
{
    public class SearchSubfile
    {
        private LocationSubfile _locationFile;
        private RoadSubfile _roadFile;

        public SearchSubfile(LocationSubfile locationFile, RoadSubfile roadFile)
        {
            this._locationFile = locationFile;
            this._roadFile = roadFile;
        }

        public void Write(Stream stream)
        {
            List<Country> sortedCountryTable = new List<Country>();
            sortedCountryTable.AddRange(_locationFile.Countries);

            List<City> sortedCityTable = new List<City>();
            sortedCityTable.AddRange(_locationFile.Cities);

            List<RoadEntry> sortedRoadTable = new List<RoadEntry>();
            foreach (Road road in _roadFile.RoadNetwork)
            {
                if (road.City != null)
                {
                    for (int i = 0; i < road.Labels.Count; i++)
                    {
                        sortedRoadTable.Add(new RoadEntry(road, (byte)i));
                    }
                }
            }

            sortedCountryTable.Sort(new CountryEntryComparer());
            sortedCityTable.Sort(new CityEntryComparer());
            sortedRoadTable.Sort(new RoadEntryComparer());

            List<int> cityToRoadTable = new List<int>();
            int currentCity = -1;
            for (int i = 0; i < sortedRoadTable.Count; i++)
            {
                RoadEntry current = sortedRoadTable[i];
                if (current.Road.City.Id > currentCity)
                {
                    while (++currentCity < current.Road.City.Id)
                    {
                        cityToRoadTable.Add(i);
                    }
                    cityToRoadTable.Add(i);
                }
            }
            while (++currentCity < _locationFile.Cities.Count)
                cityToRoadTable.Add(sortedRoadTable.Count);

            List<int> countryToCityTable = new List<int>();
            int currentCountry = -1;
            for (int i = 0; i < sortedCityTable.Count; i++)
            {
                City current = sortedCityTable[i];
                if (current.Region.Country.Id > currentCountry)
                {
                    while (++currentCountry < current.Region.Country.Id)
                    {
                        countryToCityTable.Add(i);
                    }
                    countryToCityTable.Add(i);
                }
            }
            while (++currentCountry < _locationFile.Countries.Count)
                countryToCityTable.Add(sortedCityTable.Count);


            BinaryWriter writer = new BinaryWriter(stream);
            int countryTablesOffset = 0;
            int cityTablesOffset = 0;
            int roadTablesOffset = 0;
            int offset = (int)stream.Position;

            Debug.Assert(sortedCountryTable.Count == countryToCityTable.Count);
            Debug.Assert(sortedCityTable.Count == cityToRoadTable.Count);

            writer.Write(countryTablesOffset);
            writer.Write((byte)sortedCountryTable.Count);
            writer.Write(cityTablesOffset);
            writer.Write((byte)(sortedCityTable.Count & 0xff));
            writer.Write((ushort)(sortedCityTable.Count >> 8));
            writer.Write(roadTablesOffset);
            writer.Write((int)sortedRoadTable.Count);

            countryTablesOffset = (int)stream.Position - offset;
            foreach (Country country in sortedCountryTable)
            {
                writer.Write(country.Id);
            }
            foreach (int idx in countryToCityTable)
            {
                writer.Write((byte)(idx & 0xff));
                writer.Write((ushort)(idx >> 8));
                //writer.Write((uint)(idx & 0x7fffffff));
            }
            cityTablesOffset = (int)stream.Position - offset;
            foreach (City city in sortedCityTable)
            {
                writer.Write((byte)(city.Id & 0xff));
                writer.Write((ushort)(city.Id >> 8));
            }
            foreach (int idx in cityToRoadTable)
            {
                writer.Write((uint)(idx & 0x7fffffff));
            }
            roadTablesOffset = (int)stream.Position - offset;
            foreach (RoadEntry road in sortedRoadTable)
            {
                writer.Write((byte)(road.LabelId));
                writer.Write((byte)(road.Road.Id & 0xff));
                writer.Write((ushort)(road.Road.Id >> 8));
            }
            int endOffset = (int)stream.Position;
            stream.Seek(offset, SeekOrigin.Begin);

            writer.Write(countryTablesOffset);
            writer.Write((byte)sortedCountryTable.Count);
            writer.Write(cityTablesOffset);
            writer.Write((byte)(sortedCityTable.Count & 0xff));
            writer.Write((ushort)(sortedCityTable.Count >> 8));
            writer.Write(roadTablesOffset);
            writer.Write((int)sortedRoadTable.Count);

            stream.Seek(endOffset, SeekOrigin.Begin);
        }

        private class CountryEntryComparer : IComparer<Country>
        {
            public int Compare(Country x, Country y)
            {
                return x.Label.CompareTo(y.Label);
            }
        }

        private class CityEntryComparer : IComparer<City>
        {
            public int Compare(City x, City y)
            {
                if (x.Region.Country.Id != y.Region.Country.Id)
                {
                    return x.Region.Country.Id.CompareTo(y.Region.Country.Id);
                }
                return x.Label.CompareTo(y.Label);
            }
        }

        private class RoadEntry
        {
            public RoadEntry(Road road, byte labelId)
            {
                this.Road = road;
                this.LabelId = labelId;
            }

            public Road Road { get; private set; }
            public byte LabelId { get; private set; }
        }


        private class RoadEntryComparer : IComparer<RoadEntry>
        {
            public int Compare(RoadEntry x, RoadEntry y)
            {
                if (x.Road.City != y.Road.City)
                {
                    return x.Road.City.Id.CompareTo(y.Road.City.Id);
                }
                if (x.Road.Labels[x.LabelId] == y.Road.Labels[y.LabelId])
                    return 0;
                else
                    return x.Road.Labels[x.LabelId].CompareTo(y.Road.Labels[y.LabelId]);
            }
        }
    }
}
